var gamejs = require("gamejs");
var matchModule = require("js/match");
var obstacleModule = require("js/obstacle");
var turretModule = require("js/turret");
var explosionModule = require("js/explosion");
var projectileModule = require("js/turrets/projectile");
var radarModule = require("js/radar");
var enemyModule = require("js/enemy");
var antColony = require("js/antColony");
var geModule = require("js/gameEntity");
var utils = require("js/utils");
var missions = require("js/missions");

/**
 * Map : an object representing a map. It stores a matrix of the navigable terrain
 * and the lists (as gamejs.Group object) of enemies, radars, turrets and obstacles.
 */

/**
 * Creates a map with the given dimensions.
 *
 * @param {Match} match The Match object where this object will work.
 * @param {number} width The map width, must be greater than zero.
 * @param {number} height The map height, must be greater than zero.
 * @throws {TypeError} If match isn't a Match object.
 * @throws {RangeError} If width or height aren't greater than zero.
 *
 * @constructor
 */
var Map = exports.Map = function(match, width , height) {
	if ( ! (match instanceof matchModule.Match)) {
		throw new TypeError("match isn't a valid Match object");
	}

	if (width <= 0 || height <= 0) {
		throw new RangeError("width and height must be greater than zero");
	}

	//creates the matrix of the navigable terrain
	//starts with all navigable terrain
	this.matrix = new Uint8Array(width * height);
	this.obstacles = new gamejs.sprite.Group();
	this.enemies = new gamejs.sprite.Group();
	this.turrets = new gamejs.sprite.Group();
	this.explosions = new gamejs.sprite.Group();
	this.radars = new gamejs.sprite.Group();
	this.projectiles = new gamejs.sprite.Group();
	this.size = [width, height];
	this.currentMatch = match;

	this.antColony = new antColony.AntColony(this);

	this.backgroundNight = gamejs.image.load(IMAGE_ROOT + "backgroundNight.png");
	this.backgroundDay = gamejs.image.load(IMAGE_ROOT + "backgroundDay.png");
	
	//it becomes: 1 -> night or 0 -> day
	this.isNight = 1;
};

/**
 * Creates a new Map object and adds to it the entities for the current mission
 * of the given Match object.
 * 
 * @param {Match} match The match where to retrieve the mission number.
 * @return {Map} The Map for the mission.
 */
var createMissionMap = exports.createMissionMap = function(match) {
	var tmpMap = new Map(match, MAP_WIDTH, MAP_HEIGHT);
	var initialCredit = match.credit;
	
	// add credit so we can add all what we want
	match.credit = Number.POSITIVE_INFINITY;
	
	// takes the array of entities to add to the map for the mission
	var entities = missions.MISSIONS[match.getMission() - 1].entities;
	entities.forEach(function (entity) {
		var newGe;
		if(entity.constructor == obstacleModule.Obstacle) {
			newGe = new obstacleModule.Obstacle(match, entity.center, entity.level);
		}
		else {
			var center = entity.coords || entity.center;
			newGe = new entity.constructor(match, center);
			
			if(newGe instanceof radarModule.Radar) {
				if(entity.startAngle) {
					newGe.setStartAngle(entity.startAngle);
				}
				
				if(entity.centralAngle) {
					newGe.setCentralAngle(entity.centralAngle);
				}
			}
			else if(newGe instanceof turretModule.Turret) {
				if(entity.tf) {
					newGe.setTargettingFunction(entity.tf);
				}
			}
			else {
				throw new Error("Can't add this type to the map");
			}
			
			// to the right level
			for(i = 0; i < entity.level; i++) {
				newGe.upgrade();
			}
		}
		
		// add to the map
		tmpMap.addGameEntity(newGe);
	});
	
	// restore initial credit
	match.credit = initialCredit;
	
	return tmpMap;
};

/**
 * Returns the Match object where this map work.
 *
 * @return {Match} The match where this map work.
 */
Map.prototype.getMatch = function() {
    return this.currentMatch;
};

/**
 * Returns a Group object which contains the sprites of the enemies.
 *
 * @return {gamejs.sprite.Group} The enemies Group.
 */
Map.prototype.getEnemies = function() {
	return this.enemies ;
};

/**
 * Returns a Group object which contains the sprites of the obstacles.
 *
 * @return {gamejs.sprite.Group} The obstacles Group.
 */
Map.prototype.getObstacles = function() {
	return this.obstacles;
};

/**
 * Returns a Group object which contains the sprites of the turrets.
 *
 * @return {gamejs.sprite.Group} The turrets Group.
 */
Map.prototype.getTurrets = function() {
	return this.turrets;
};

/**
 * Returns a Group object which contains the sprites of the radars.
 *
 * @return {gamejs.sprite.Group} The radars Group.
 */
Map.prototype.getRadars = function() {
	return this.radars;
};

/**
 * Returns a Group object which contains the sprites of the explosions.
 *
 * @return {gamejs.sprite.Group} The explosions Group.
 */
Map.prototype.getExplosions = function() {
	return this.explosions;
};

/**
 * Returns the map size.
 *
 * @returns {[number, number]}  The map size in [width, height] format.
 */
Map.prototype.getSize = function() {
	return this.size;
};

/**
 * Returns the ant colony that has been built upon this map
 *
 * @return {AntColony} The ant colony object
 */
Map.prototype.getAntColony = function() {
	return this.antColony;
}

/**
 * Checks if a point is inside the map.
 *
 * @param {[number, number]} The coordinates of the point to check, in [x, y] format.
 * @return {boolean} True if the given point is inside the map, false otherwise.
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 */
Map.prototype.isInside = function(coordinates) {
	if( ! utils.areCoordinates(coordinates)) {
		throw new TypeError("Coordinates not in the correct form");
	}

	return (((coordinates[X] >= 0) && (coordinates[X] < this.size[WIDTH])) &&//x is in range
		((coordinates[Y] >= 0) && (coordinates[Y] < this.size[HEIGHT])));//y is in range
};

/**
 * Fills a pixel to make it not navigable.
 *
 * @param {[number, number]} coordinates The pixel coordinates as vector [x, y].
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If coordinates isn't inside the map.
 */
Map.prototype.fill = function(coordinates) {
	if( ! this.isInside(coordinates)) {
		throw new RangeError("Invalid coordinates");
	}
	this.matrix[coordinates[X] + coordinates[Y] * this.size[X]] = 1;
};

/**
 * Clears a pixel to make it navigable.
 *
 * @param {[number, number]} coordinates The pixel coordinates as vector [x, y].
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If coordinates isn't inside the map.
 */
Map.prototype.clear = function(coordinates) {
	if( ! this.isInside(coordinates)) {
		throw new RangeError("Invalid coordinates");
	}
	this.matrix[coordinates[X] + coordinates[Y] * this.size[X]] = 0;
};

/**
 * Tells if the pixel is filled(not navigable).
 *
 * @param {[number, number]} coordinates The pixel coordinates as vector [x, y].
 * @returns {boolean} True if the pixel is filled(not navigable), false otherwise.
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If coordinates isn't inside the map.
 */
Map.prototype.isFilled = function(coordinates) {
	if( ! this.isInside(coordinates)) {
		throw new RangeError("Invalid coordinates");
	}

	return !! this.matrix[coordinates[X] + coordinates[Y] * this.size[X]];
};

/**
 * Fills a circle making not navigable its area.
 *
 * @param {[number, number]} center The center of the circle to use as vector [x, y].
 * @param {number} radius The radius of the circle to use, must be greater than zero.
 * @throws {TypeError} If center coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If center isn't inside the map or if radius isn't greater than zero.
 */
Map.prototype.fillCircle = function(center, radius) {
	this.fillCircleWith(center, radius, false);
};

/**
 * Clears a circle making navigable its area.
 *
 * @param {[number, number]} center The center of the circle to use as vector [x, y].
 * @param {number} radius The radius of the circle to use, must be greater than zero.
 * @throws {TypeError} If center coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If center isn't inside the map or if radius isn't greater than zero.
 */
Map.prototype.clearCircle = function(center, radius) {
	this.fillCircleWith(center, radius, true);
};

Map.prototype.setDay = function(){
	this.isNight = 0;
};


Map.prototype.setNight = function(){
	this.isNight = 1;
};

/**
 * Fills a circle with the given value.
 *
 * @param {[number, number]} center The center of the circle to use as vector [x, y].
 * @param {number} radius The radius of the circle to use, must be greater than zero.
 * @param {boolean} navigable Sets the value to give to the circle area:
 * false for fill(not navigable); true for clear(navigable).
 * @throws {TypeError} If center coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If center isn't inside the map or if radius isn't greater than zero.
 */
Map.prototype.fillCircleWith = function(center, radius, navigable) {
	if( ! this.isInside(center)) {
		throw new RangeError("Invalid center coordinates");
	}
	if( ! (radius > 0)) {
		throw new RangeError("radius must be greater than zero");
	}

	//work on the square circumscribed about the circle
	for(var x = center[X] - radius; x < center[X] + radius; x++) {
		for(var y = center[Y] - radius; y < center[Y] + radius; y++) {
			//calculates the distance from center with Pythagoras
			var dis = utils.distance([x, y], center);
			if((dis <= radius) && (this.isInside([x, y]))) {
				if (navigable) {
					this.clear([x, y]);
				}
				else {
					this.fill([x, y]);
				}
			}
		}
	}
};

/**
 * Fills a rectangle making not navigable its area.
 *
 * @param {gamejs.Rect} rect The rectangle to use.
 * @throws {TypeError} If rect isn't a gamejs.Rect object.
 */
Map.prototype.fillRect = function(rect) {
	this.fillRectWith(rect, false);
};

/**
 * Clears a rectangle making not navigable its area.
 *
 * @param {gamejs.Rect} rect The rectangle to use.
 * @throws {TypeError} If rect isn't a gamejs.Rect object.
 */
Map.prototype.clearRect = function(rect) {
	this.fillRectWith(rect, true);
};

/**
 * Fills a rectangle with the given value.
 *
 * @param {gamejs.Rect} rect The rectangle to use.
 * @param {boolean} navigable Sets the value to give to the rectangle area:
 * false for fill(not navigable); true for clear(navigable).
 * @throws {TypeError} If rect isn't a gamejs.Rect object.
 */
Map.prototype.fillRectWith = function(rect, navigable) {
	if( ! rect instanceof gamejs.Rect) {
		throw new TypeError("rect is not a gamejs.Rect object");
	}

	//work from corner to corner
	for(var x = rect.topleft[X]; x <= rect.bottomright[X]; x++) {
		for(var y = rect.topleft[Y]; y <= rect.bottomright[Y]; y++) {
			if (this.isInside([x, y])) {
				if (navigable) {
					this.clear([x, y]);
				}
				else {
					this.fill([x, y]);
				}
			}
		}
	}
};

/**
 * Adds an object to this map, adding it to the correct Group.
 *
 * NOTE : this method MUST NOT be used out of the Map object, use
 * the addGameEntity(..) instead.
 *
 * @param {Object} obj The object to add.
 * @return {gamejs.sprite.Group} The Group where obj is been added.
 * @throws {TypeError} If obj is not of a type that this map can managed.
 */
Map.prototype.addToGroup = function (obj) {
	// checks the type of the given object
	// if is a type manageable by this map adds the object to the correct
	// Group, then returns the Group
	if (obj instanceof enemyModule.Enemy) {
		this.enemies.add(obj);

		return this.enemies;
	}

	if (obj instanceof obstacleModule.Obstacle) {
		this.obstacles.add(obj);

		return this.obstacle;
	}

	if (obj instanceof radarModule.Radar) {
		this.radars.add(obj);

		return this.radars;
	}

	if (obj instanceof turretModule.Turret) {
		this.turrets.add(obj);

		return this.turrets;
	}

	// if we are here we haven't added obj to any Group
	throw new TypeError("obj isn't of a type that can be managed");
};

/**
 * Fills the obstructive area of a GameEntity object.
 * This method works fine if the given gameEntity is already been added
 * to his Group.
 *
 * NOTE : this method MUST NOT be used out of the Map object, use the
 * addGameEntity(..) method and it automatically fills the obstructive area.
 *
 * @param {GameEntity} gameEntity The GameEntity object that has to fill
 * the underlying area.
 * @throws {Error(InvalidPositionError)} If the obstructive area is occupied.
 */
Map.prototype.fillObstructiveArea = function (gameEntity) {
	// obstacles have a different behavior cause they can overlap each others
	var isObstacle = ( gameEntity instanceof obstacleModule.Obstacle);

	// checks if the underlying area is not occupied by radars, turrets,
	// obstacles (this last only if gameEntity is not an obstacle)
	var collRadar = gamejs.sprite.spriteCollide(
		gameEntity, this.radars, false, utils.collideGeneric
	);

	var collTurret = gamejs.sprite.spriteCollide(
		gameEntity, this.turrets, false, utils.collideGeneric
	);

	var collObstacle = [];

	if ( ! isObstacle) {
		// only obstacles can overlap each others
		collObstacle = gamejs.sprite.spriteCollide(
			gameEntity, this.obstacles, false, utils.collideGeneric
		);
	}

	// gameEntity surely collides with itself, so if it collides with just
	// one object it's all ok
	if ((collRadar.length + collTurret.length + collObstacle.length) > 1) {
		throw new Error("InvalidPositionError : this area is occupied");
	}

	if (gameEntity.getShape() === SHAPE_RECTANGULAR) {
		// fill the area
		this.fillRect(gameEntity.getRect());
	} else {
		// not a rect -> it's a circle
		var center = gameEntity.getCenter();
		var radius = gameEntity.getObstructiveRadius();
		// fill the area
		this.fillCircle(center, radius);
	}
};

/**
 * Adds a GameEntity object to the map.
 *
 * @param {GameEntity} gameEntity The GameEntity object to add.
 * @throws {TypeError} If gameEntity isn't a GameEntity object, or it is
 * a GameEntity object but not of a type that this map can managed.
 * @throws {Error(InvalidOperationError)} If the player hasn't enough credits
 * to add this object.
 * @throws {Error(InvalidPositionError)} If the obstructive area is occupied.
 */
Map.prototype.addGameEntity = function (gameEntity) {
	if ( ! (gameEntity instanceof geModule.GameEntity)) {
		throw new TypeError("gameEntity isn't a valid GameEntity object");
	}

	// checks if enough credit
	var price = geModule.GameEntity.getBuyPrice(
		gameEntity.getUpgradeList(), gameEntity.getLevel()
	);

	if ( ! this.currentMatch.hasCredit(price)) {
		throw new Error("InvalidOperationError : not enough credits");
	}

	// regardless if this object fills the map, add it to the correct Group
	var group = this.addToGroup(gameEntity);

	if (gameEntity.fillsMap()) {
		// fill the obstructive area
		try {
			this.fillObstructiveArea(gameEntity);
		}
		catch (e) {
			// this area is occupied, but we have already added the object to
			// a group, remove it
			group.remove(gameEntity);
			throw e;
		}
	}

	// object correctly added, pay it
	this.currentMatch.changeCredit( - price);
};

/**
 * Removes an object from this map, removing it from the correct Group.
 *
 * NOTE : this method MUST NOT be used out of the Map object, use
 * the removeGameEntity(..) instead.
 *
 * @param {Object} obj The object to remove.
 * @throws {Error(InvalidOperationError)} If obj isn't present in the map.
 * @throws {TypeError} If obj is not of a type that this map can managed.
 */
Map.prototype.removeFromGroup = function (obj) {
	// checks the type of the given object
	// if is a type manageable by this map removes
	// the object from the correct Group, if it's present
	if (obj instanceof enemyModule.Enemy) {
		if( ! this.enemies.has(obj)) {
			throw new Error("InvalidOperationError : obj isn't present in this map");
		}
		this.enemies.remove(obj);

		return;
	}

	if (obj instanceof obstacleModule.Obstacle) {
		if( ! this.obstacles.has(obj)) {
			throw new Error("InvalidOperationError : obj isn't present in this map");
		}
		this.obstacles.remove(obj);

		return;
	}

	if (obj instanceof radarModule.Radar) {
		if( ! this.radars.has(obj)) {
			throw new Error("InvalidOperationError : obj isn't present in this map");
		}
		this.radars.remove(obj);

		return;
	}

	if (obj instanceof turretModule.Turret) {
		if( ! this.turrets.has(obj)) {
			throw new Error("InvalidOperationError : obj isn't present in this map");
		}
		this.turrets.remove(obj);

		return;
	}

	// if we are here we haven't removed obj from any Group
	throw new TypeError("obj isn't of a type that can be managed");
};

/**
 * Clears the obstructive area of a GameEntity object.
 * This method works fine if the given gameEntity is already been removed
 * from his Group.
 *
 * NOTE : this method MUST NOT be used out of the Map object, use the
 * removeGameEntity(..) method and it automatically clears the obstructive area.
 *
 * @param {GameEntity} gameEntity The GameEntity object that has to clear
 * the underlying area.
 */
Map.prototype.clearObstructiveArea = function (gameEntity) {
	// obstacles have a different behavior cause they can overlap each others
	var isObstacle = (gameEntity instanceof obstacleModule.Obstacle);

	if (gameEntity.getShape() === SHAPE_RECTANGULAR) {
		this.clearRect(gameEntity.getRect());// clear the area
	}
	else {
		// not a rect -> it's a circle
		var center = gameEntity.getCenter();
		var radius = gameEntity.getObstructiveRadius();
		this.clearCircle(center, radius);// clear the area
	}

	if (isObstacle) {
		// obstacles can overlap each others so we could have clear an area
		// occupied by another obstacles
		var collObstacles = gamejs.sprite.spriteCollide(
			gameEntity, this.obstacles, false, utils.collideGeneric
		);

		if (collObstacles.length > 0) {
			//there are others obstacles in the same area
			//refill them
			collObstacles.forEach( function (obs) {
				if (obs.getShape() === SHAPE_RECTANGULAR) {
					// re-fill the area
					this.fillRect(obs.getRect());
				} else {
					// not a rect -> it's a circle
					var center = obs.getCenter();
					var radius = obs.getObstructiveRadius();
					// re-fill the area
					this.fillCircle(center, radius);
				}
			}, this);
		}
	}
};

/**
 * Removes a GameEntity object from the map.
 *
 * @param {GameEntity} gameEntity The GameEntity object to remove.
 * @throws {TypeError} If gameEntity isn't a GameEntity object, or it is
 * a GameEntity object but not of a type that this map can managed.
 * @throws {Error(InvalidOperationError)} If gameEntity isn't present in the
 * map or if it's present but isn't saleable.
 */
Map.prototype.removeGameEntity = function(gameEntity) {
	if ( ! (gameEntity instanceof geModule.GameEntity)) {
		throw new TypeError("gameEntity isn't a valid GameEntity object");
	}

	if (gameEntity.isSaleable()) {
		// remove the object from the correct Group
		this.removeFromGroup(gameEntity);

		if (gameEntity.fillsMap()) {
			// clear the obstructive area
			this.clearObstructiveArea(gameEntity);
		}

		// object correctly removed, get the money
		this.currentMatch.changeCredit(gameEntity.getResaleValue());
	}
	else {
		throw new Error("InvalidOperationError : can't remove this gameEntity");
	}
};

/**
 * Adds an Explosion object to the map.
 *
 * @param {Explosion} explosion The Explosion object to add.
 */
Map.prototype.addExplosion = function(explosion) {
	this.explosions.add(explosion);
};

/**
 * Tells if there are radars in the map.
 *
 * @return {boolean} True if there are radars in the map, false otherwise.
 */
Map.prototype.hasRadars = function() {
	return (this.radars.sprites().length > 0);
};

/**
 * Tells if there are turrets in the map.
 *
 * @return {boolean} True if there are turrets in the map, false otherwise.
 */
Map.prototype.hasTurrets = function() {
	return (this.turrets.sprites().length > 0);
};

/**
 * Return the projectiles group.
 *
 * @return {Group} The projectiles group.
 */
Map.prototype.getProjectiles= function() {
	return this.projectiles;
};

/**
 * Adds a projectile on the map
 *
 * @param {Projectile} projectile The Projectile to add on the map
 */
Map.prototype.addProjectile = function(projectile) {
	this.projectiles.add(projectile);
};

/**
 * Removes a projectile from the map
 *
 * @param {Projectile} projectile The Projectile to remove
 *
 * @throws {Error} If the map doesn't have the projectile
 */
Map.prototype.removeProjectile = function(projectile){
	// If the projectile is in the Map remove it
	if (this.projetiles.has(projectile)) {
		this.projectiles.remove(projectile);
	}
	// if the projectile isn't in the Map raise an exception
	else {
		throw new Error("This Map doesn't have the searched projectile.");
	}
};

/**
 * Provides all the valid adjacent points of the given point.
 *
 * @param {[numer, number]} coordinates The coordinates of the point to use as vector [x, y].
 * @return {Array} Array of coordinates of the the valid adjacent points as vector [x, y].
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If coordinates isn't inside the map.
 */
Map.prototype.adjacent = function(coordinates) {
	if( ! this.isInside(coordinates)) {
		throw new RangeError("Invalid coordinates");
	}
	//takes all the possible directions
	var directions = [
		[0, -1], [-1, 0], [1, 0], [0, 1],
		[-1, -1], [1, -1], [1, 1], [-1, 1]
	];

	//takes adjacent points
	var allAdjacent = directions.map( function(dir) {
		return [(coordinates[X] + dir[X]), (coordinates[Y] + dir[Y])];
	});

	//removes if not in the map or if filled
	var validAdjacent = allAdjacent.filter( function(coordinates) {
		return ((this.isInside(coordinates)) &&//in map
			( ! this.isFilled(coordinates)));//not filled
	}, this);

	return validAdjacent;
};

/**
 * Provides all the valid points reachable from the given coordinates point with stepLength
 * single movements to north, north-east, east, south-east, south, south-west, west, north-west.
 *
 * @param {[numer, number]} coordinates The coordinates of the point to use as vector [x, y].
 * @param {number} stepLength The number of single movements to do.
 * @return {Array} Array of coordinates of the the valid reachable points as vector [x, y].
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If coordinates isn't inside the map.
 */
Map.prototype.reachable = function(coordinates, stepLength) {
	if( ! this.isInside(coordinates)) {
		throw new RangeError("Invalid coordinates");
	}
	//takes all the possible directions
	var directions = [
		[0, -stepLength], [-stepLength, 0], [stepLength, 0], [0, stepLength],
		[-stepLength, -stepLength], [stepLength, -stepLength], [stepLength, stepLength], [-stepLength, stepLength]
	];

	//takes reachable points
	var allReachable = directions.map( function(dir) {
		return [(coordinates[X] + dir[X]), (coordinates[Y] + dir[Y])];
	});

	//removes if not in the map or if filled
	var validReachable = allReachable.filter( function(coordinates) {
		return ((this.isInside(coordinates)) &&//in map
			( ! this.isFilled(coordinates)));//not filled
	}, this);

	return validReachable;
};

/**
 * Returns the actual distance from two adjacent points.
 *
 * @param {[numer, number]} startPoint Coordinates of the point from where to start, as vector [x, y].
 * @param {[numer, number]} endPoint Coordinates of the point to reach, as vector [x, y].
 * @return {number} The actual distance between the two points.
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If coordinates isn't inside the map.
 */
Map.prototype.actualDistance = function(startPoint, endPoint) {
	if( ! this.isInside(startPoint) ||  ! this.isInside(endPoint)) {
		throw new RangeError("Invalid coordinates");
	}

	if((startPoint[X] != endPoint[X]) && (startPoint[Y] != endPoint[Y])) {
		return 1.41;
	}
	else {
		return 1;
	}
};

/**
 * Returns the estimated distance from two points, using the Manhattan heuristic.
 *
 * @param {[numer, number]} startPoint Coordinates of the point from where to start, as vector [x, y].
 * @param {[numer, number]} endPoint Coordinates of the point to reach, as vector [x, y].
 * @return {number} The estimated distance between the two points.
 * @throws {TypeError} If coordinates isn't in the correct form [number, number].
 * @throws {RangeError} If coordinates isn't inside the map.
 */
Map.prototype.estimatedDistance = function(startPoint, endPoint) {
	if( ! this.isInside(startPoint) ||  ! this.isInside(endPoint)) {
		throw new RangeError("Invalid coordinates");
	}
	//using the Manhattan heuristic
	var dx = Math.abs(startPoint[X] - endPoint[X]);
	var dy = Math.abs(startPoint[Y] - endPoint[Y]);

	return (dx + dy);
};

/**
 * Updates the map calling update(msDuration) on all the Group; it follows
 * this order: updates the obstacles, updates the turrets, updates the radars,
 * updates the enemies.
 *
 * @param {number} msDuration The time past from the last call, in ms.
 */
Map.prototype.update = function(msDuration) {
	//this.obstacles.update(msDuration);nothing to update here
	this.turrets.update(msDuration);
	this.radars.update(msDuration);
	this.enemies.update(msDuration);
	this.projectiles.update(msDuration);
	this.explosions.update(msDuration);
};

/**
 * Draws the map in the given surface, following this order:
 * draws the background, draws the obstacles, draws the turrets,
 * draws the radars, draws the enemies.

 * @param {gamejs.Surface} surface The Surface object where to draw.
 */
Map.prototype.draw = function(surface) {
	surface.blit(this.isNight ? this.backgroundNight : this.backgroundDay);
	//this.obstacles.draw(surface);
	//the obstacle image has a transparent border area
	//this TEMPORARY function draws the correct border
	this.obstacles.sprites().forEach(function (obs) {
		obs.draw(surface);
		//gamejs.draw.circle(surface, "#C0C0C0", obs.getCenter(), obs.getObstructiveRadius(), 1);
	});
	this.turrets.draw(surface);
	this.radars.draw(surface);
	this.enemies.draw(surface);
	this.projectiles.draw(surface);
	this.explosions.draw(surface);
};